﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using LostLib.Win32;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;

namespace LostLib.Windows
{
	public partial class WindowsManagerWindow: Form
	{
		private static class KeyCodes
		{
			public static Keys MouseRestrictionsInvert = 
				Keys.L | Keys.Alt | Keys.Control;
			public static Keys HideCurrentWindow =
				Keys.H | Keys.Alt | Keys.Control;
		}

		public WindowsManagerWindow( )
		{
			InitializeComponent( );

			mKeyHook = new KeyboardHook( );
			mKeyHook.KeyPressed += new KeyEventHandler(mKeyHook_KeyPressed);

			Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

			labelHideWindowHotkey.Text = KeyCodes.HideCurrentWindow.ToString();
		}

		void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
		{
			MessageBox.Show(this, e.Exception.Message, e.Exception.GetType( ).Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
		}

		void mKeyHook_KeyPressed(object sender, KeyEventArgs e)
		{
			if (e.KeyData == KeyCodes.MouseRestrictionsInvert)
			{
				InvertWindowRestrictionStatus(ScreenWindow.GetForegroundWindow( ));
				return;
			}

			if (e.KeyData == KeyCodes.HideCurrentWindow)
			{
				var window = ScreenWindow.GetForegroundWindow();
				if (window.Handle == this.Handle) return;
				window.Hide();
				listHiddenWindows.Items.Add(window);
				return;
			}
		}

		private KeyboardHook mKeyHook;

		private void ManualDispose( )
		{
			mKeyHook.Dispose( );
		}

		#region Mouse Restrictions
		private void numericMouseRestrictionsApplyInterval_ValueChanged(object sender, EventArgs e)
		{
			timerMouseRestrictionsDelay.Interval = (int)numericMouseRestrictionsApplyInterval.Value;
		}

		private void textHotkey_Click(object sender, EventArgs e)
		{
			keySelectDialog.Key = KeyCodes.MouseRestrictionsInvert;
			if (keySelectDialog.ShowDialog( ) == DialogResult.OK)
				ChangeMouseRestrictionsInvertHotkey(keySelectDialog.Key);
		}

		private void ChangeMouseRestrictionsInvertHotkey(Keys key)
		{
			mKeyHook.UnregisterHotKey(KeyCodes.MouseRestrictionsInvert);
			KeyCodes.MouseRestrictionsInvert = key;
			labelMouseRestrictionHotkey.Text = key.ToString( );
			mKeyHook.RegisterHotKey(key);
		}

		private void timerMouseRestrictionsDelay_Tick(object sender, EventArgs e)
		{
			var window = ScreenWindow.GetForegroundWindow( );
			if (listMouseRestricted.Items.Contains(window))
				Mouse.ClipCursor(new Rect {
					Left = window.ClientLeft, Top = window.ClientTop,
					Width = window.ClientWidth, Height = window.ClientHeight,
				});

			lock (removeList)
			{
				foreach (var win in removeList)
					listMouseRestricted.Items.Remove(win);

				removeList.Clear();
			}
		}

		private void InvertWindowRestrictionStatus(ScreenWindow window)
		{
			if (listMouseRestricted.Items.Contains(window))
			{
				listMouseRestricted.Items.Remove(window);
				Mouse.ClipCursor(null);
				if (checkShowMouseLockNotifications.Checked)
				{
					notifyIcon.BalloonTipIcon = ToolTipIcon.Info;
					notifyIcon.BalloonTipText = string.Format("disabled mouse cursor clipping for {0}", window);
					notifyIcon.ShowBalloonTip(500);
				}
			} else
			{
				listMouseRestricted.Items.Add(window);

				var process = Process.GetProcessById(window.ProcessID);
				if (!processes.Exists(proc => proc.Id == process.Id))
				{
					process.EnableRaisingEvents = true;
					process.Exited += new EventHandler(process_Exited);
					processes.Add(process);
				}

				if (checkShowMouseLockNotifications.Checked)
				{
					notifyIcon.BalloonTipIcon = ToolTipIcon.Info;
					notifyIcon.BalloonTipText = string.Format("enabled mouse cursor clipping for {0}", window);
					notifyIcon.ShowBalloonTip(500);
				}
			}
		}

		List<Process> processes = new List<Process>();

		List<ScreenWindow> removeList = new List<ScreenWindow>();

		void process_Exited(object sender, EventArgs e)
		{
			var process = sender as Process;

			int id = process.Id;

			process.Dispose();
			processes.Remove(process);

			lock (removeList)
			{
				foreach (ScreenWindow window in listMouseRestricted.Items)
					if (window.ProcessID == id) removeList.Add(window);
			}
		}

		static string AppData
		{
			get
			{
				var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
				if (!appData.EndsWith(Path.DirectorySeparatorChar.ToString()))
					appData += Path.DirectorySeparatorChar;
				return appData;
			}
		}

		private void LoadMouseRestrictionsConfig( )
		{
			try
			{
				using (var reader = XmlReader.Create(AppData + "mouse.xml"))
				{
					var serializer = new XmlSerializer(typeof(MouseRestrictionsConfig));

					var config = serializer.Deserialize(reader) as MouseRestrictionsConfig;

					numericMouseRestrictionsApplyInterval.Value = config.Interval;
					numericMouseRestrictionsApplyInterval_ValueChanged(config, new EventArgs( ));

					checkShowMouseLockNotifications.Checked = config.ShowNotifications;

					KeyCodes.MouseRestrictionsInvert = config.Hotkey;
				}
			} catch (FileNotFoundException)
			{
			}
		}
		private void SaveMouseRestrictionConfig( )
		{
			using (var writer = XmlWriter.Create(AppData + "mouse.xml"))
			{
				var serializer = new XmlSerializer(typeof(MouseRestrictionsConfig));

				var config = new MouseRestrictionsConfig {
					ShowNotifications = checkShowMouseLockNotifications.Checked,
					Interval = (int)numericMouseRestrictionsApplyInterval.Value,
					Hotkey = KeyCodes.MouseRestrictionsInvert,
				};

				serializer.Serialize(writer, config);

				writer.Close( );
			}
		}
		#endregion Mouse Restrictions

		#region Windows
		private void buttonWindowsRefresh_Click(object sender, EventArgs e)
		{
			treeWindows.BeginUpdate( );

			treeWindows.Nodes.Clear( );
			treeWindows.Nodes.Add(AddWindow(ScreenWindow.NullWindow));

			treeWindows.EndUpdate( );
		}

		private void AddWindowBorderStyles( )
		{
			var items = comboBorderStyles.Items;
			items.Add(FormBorderStyle.None);
			items.Add(FormBorderStyle.Sizable);
			items.Add(FormBorderStyle.FixedDialog);
			items.Add(FormBorderStyle.FixedSingle);
			items.Add(FormBorderStyle.Fixed3D);
		}

		private TreeNode AddWindow(ScreenWindow window)
		{
			var result = new TreeNode(window.ToString( ));
			result.Tag = window;
			foreach (var child in window.GetChilds(false))
				result.Nodes.Add(AddWindow(child));
			return result;
		}

		ScreenWindow mSelectedWindow;
		ScreenWindow SelectedWindow
		{
			get
			{
				return mSelectedWindow;
			}
			set
			{
				if ((mSelectedWindow ?? value) == null)
					return;
				if (value == null)
				{
					groupWindowProperties.Enabled = false;
				} else
				{
					if (value.Equals(mSelectedWindow))
						return;
					groupWindowProperties.Enabled = true;
					numericWindowX.Value = (decimal)value.Left;
					numericWindowY.Value = (decimal)value.Top;
					comboBorderStyles.SelectedItem = value.BorderStyle;
				}
				mSelectedWindow = value;
			}
		}

		private void treeWindows_AfterSelect(object sender, TreeViewEventArgs e)
		{
			SelectedWindow = treeWindows.SelectedNode == null
				? null
				: treeWindows.SelectedNode.Tag as ScreenWindow;
			//mSelectingWindow = false;
		}
		//private bool mSelectingWindow;
		private void buttonApply_Click(object sender, EventArgs e)
		{
			var newStyle = (FormBorderStyle)comboBorderStyles.SelectedItem;
			SelectedWindow.BorderStyle = newStyle;

			SelectedWindow.SetPosition(
					(int)numericWindowX.Value,
					(int)numericWindowY.Value);


		}
		private void buttonHideWindow_Click(object sender, EventArgs e)
		{
			mSelectedWindow.Hide( );
		}
		private void buttonShowWindow_Click(object sender, EventArgs e)
		{
			mSelectedWindow.Show( );
		}
		#endregion Windows

		private void WindowsManagerWindow_Load(object sender, EventArgs e)
		{
			LoadMouseRestrictionsConfig( );
			LoadHiddenWindowsConfig();

			notifyIcon.Icon = this.Icon;

			mKeyHook.RegisterHotKey(KeyCodes.MouseRestrictionsInvert);
			mKeyHook.RegisterHotKey(KeyCodes.HideCurrentWindow);

			labelMouseRestrictionHotkey.Text = KeyCodes.MouseRestrictionsInvert.ToString( );
			labelHideWindowHotkey.Text = KeyCodes.HideCurrentWindow.ToString();

			AddWindowBorderStyles( );
		}

		private void WindowsManagerWindow_FormClosed(object sender, FormClosedEventArgs e)
		{
			var consoleWindow = ScreenWindow.GetConsoleWindow( );
			consoleWindow.Show( );
			SaveMouseRestrictionConfig( );
			SaveHiddenWindowsConfig();
			Application.Exit( );
		}

		#region NotifyIcon Handling
		protected override void WndProc(ref Message m)
		{
			const UInt32 WM_SYSCOMMAND = 0x112;
			const UInt32 SC_MINIMIZE = 0xF020;

			if ((m.Msg == WM_SYSCOMMAND) && ((UInt32)m.WParam == SC_MINIMIZE))
			{
				this.Hide( );
				return;
			}

			base.WndProc(ref m);
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Application.Exit( );
		}

		private void notifyIcon_DoubleClick(object sender, EventArgs e)
		{
			this.Show();
			this.Activate( );
		}
		#endregion

		private void treeWindows_BeforeSelect(object sender, TreeViewCancelEventArgs e)
		{
			//mSelectingWindow = true;
		}

		#region Windows hiding
		private void listHiddenWindows_DoubleClick(object sender, EventArgs e)
		{
			var window = listHiddenWindows.SelectedItem as ScreenWindow;
			window.Show();
			listHiddenWindows.Items.Remove(window);
		}

		private void labelHideWindowShortcut_Click(object sender, EventArgs e)
		{
			keySelectDialog.Key = KeyCodes.HideCurrentWindow;
			if (keySelectDialog.ShowDialog() == DialogResult.OK)
				ChangeHideWindowHotkey(keySelectDialog.Key);
		}

		private void ChangeHideWindowHotkey(Keys key)
		{
			mKeyHook.UnregisterHotKey(KeyCodes.HideCurrentWindow);
			KeyCodes.HideCurrentWindow = key;
			labelHideWindowHotkey.Text = key.ToString();
			mKeyHook.RegisterHotKey(key);
		}

		private void LoadHiddenWindowsConfig()
		{
			try
			{
				using (var reader = XmlReader.Create(AppData + "hiddenWindows.xml"))
				{
					var serializer = new XmlSerializer(typeof(HiddenWindowsConfig));

					var config = serializer.Deserialize(reader) as HiddenWindowsConfig;

					KeyCodes.HideCurrentWindow = config.Hotkey;
				}
			} catch (FileNotFoundException)
			{
			}
		}
		private void SaveHiddenWindowsConfig()
		{
			using (var writer = XmlWriter.Create(AppData + "hiddenWindows.xml"))
			{
				var serializer = new XmlSerializer(typeof(HiddenWindowsConfig));

				var config = new HiddenWindowsConfig
				{
					Hotkey = KeyCodes.HideCurrentWindow,
				};

				serializer.Serialize(writer, config);

				writer.Close();
			}
		}
		#endregion Windows hiding
	}

	[Serializable]
	public class WindowFilter
	{
		public string Text
		{
			get;
			set;
		}
		public string ClassName
		{
			get;
			set;
		}

		public override string ToString( )
		{
			return string.Format("[{0}] {1}", ClassName ?? "??", Text ?? "??");
		}
	}
}
